/* Copyright (c) 2007 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. 
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRENTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 * $LastChangedRevision: 2290 $
 */ 
        
/** @file
 *
 * Device source code for the Wireless Desktop Protocol. 
 *
 * @author Lasse Olsen
 *
 */

#include "wdp_device.h"
#include <string.h>

//***************************************************************************** 
// Misc. macros
//*****************************************************************************

#define WDP_GET_BIT(a, b) ((a >> b) & 0x01)
#define WDP_CLEAR_BIT(a, b) (a &= ~(1 << b))
#define WDP_SET_BIT(a, b) (a |= (1 << b)) 

typedef enum
{
  WDP_CONNECTED,
  WDP_SEND_KEEP_ALIVE
} wdp_status_register;

static xdata uint8_t wdp_status;

static xdata uint16_t wdp_keep_alive_counter;
                                              
static xdata fap_tx_rx_struct_t wdp_general_buffer;

static const uint8_t wdp_pairing_ch_tab[FAP_CH_TAB_SIZE] = WDP_PAIRING_CHANNELS;
  
void wdp_send_keep_alive(); 
bool wdp_transmit_tx_buffer_and_wait(fap_tx_rx_struct_t *dat, uint16_t max_latency);

typedef struct
{
  uint8_t pairing_address[FAP_ADDRESS_WIDTH];
}dev_static_setup_t;

xdata dev_static_setup_t wdp_dev_static_setup =
{
  WDP_PAIRING_ADDRESS,
};

typedef struct
{
  uint8_t dev_address[FAP_ADDRESS_WIDTH];
  wdp_dev_types_t dev_type;
  uint16_t ka_interval;
}dev_dynamic_setup_t;

static xdata dev_dynamic_setup_t wdp_dev_dynamic_setup;

void wdp_device_init(wdp_dev_types_t dev_type)
{
  fap_init(); 
  
  WDP_CLEAR_BIT(wdp_status, WDP_CONNECTED);
  WDP_CLEAR_BIT(wdp_status, WDP_SEND_KEEP_ALIVE);
   
  fap_set_address(FAP_DEVICE0, wdp_dev_static_setup.pairing_address);

  wdp_dev_dynamic_setup.dev_type = dev_type;

  switch(dev_type)
  {
    case WDP_MOUSE:
      wdp_dev_dynamic_setup.ka_interval = WDP_MOUSE_KA_INTERVAL;
      break;
    case WDP_KEYBOARD:
      wdp_dev_dynamic_setup.ka_interval = WDP_KEYBOARD_KA_INTERVAL;
      break;
    case WDP_REMOTE:
      wdp_dev_dynamic_setup.ka_interval = WDP_REMOTE_KA_INTERVAL;
      break;
    default:
      break;
  }
}

void wdp_device_process_events()
{
  if(WDP_GET_BIT(wdp_status, WDP_CONNECTED))
  {
    if(!fap_tx_success())
    {
      WDP_CLEAR_BIT(wdp_status, WDP_CONNECTED);
    }
 
    if(WDP_GET_BIT(wdp_status, WDP_SEND_KEEP_ALIVE))
    { 
      WDP_CLEAR_BIT(wdp_status, WDP_SEND_KEEP_ALIVE);
      wdp_send_keep_alive();   
    }
  }
}

void wdp_timer_isr_function()
{   
  if(WDP_GET_BIT(wdp_status, WDP_CONNECTED))
  {
    if(wdp_keep_alive_counter < wdp_dev_dynamic_setup.ka_interval)    
    {
      wdp_keep_alive_counter++; 
    }
    else
    {
      wdp_keep_alive_counter=0;                           // Reset keep alive counter
      WDP_SET_BIT(wdp_status, WDP_SEND_KEEP_ALIVE); 
    }
  }  
}

void wdp_device_connect()
{ 
  WDP_SET_BIT(wdp_status, WDP_CONNECTED); 
  wdp_send_keep_alive();
}

void wdp_device_disconnect()
{
  WDP_CLEAR_BIT(wdp_status, WDP_CONNECTED);  
}

bool wdp_device_send_data(uint8_t *dat, uint8_t length)
{
  if(length <= WDP_MAX_UL_PL_LENGTH)
  {
    wdp_general_buffer.pipe = FAP_DEVICE1;
    wdp_general_buffer.pl[WDP_TYPE_ID] = USER_DATA;
    memcpy(&wdp_general_buffer.pl[WDP_APP_PL_START], dat, length); 
    wdp_general_buffer.pl_length = (WDP_APP_PL_START + length);           
  
    wdp_keep_alive_counter=0;             
  
    return fap_tx_data(&wdp_general_buffer, WDP_TX_DATA_TIMEOUT);
  }
  else
  {
    return false;
  }
}

bool wdp_device_get_downlink_data(uint8_t *dst, uint8_t *length)
{
  if(fap_read_rx_fifo(&wdp_general_buffer))
  {
    memcpy(dst, &wdp_general_buffer.pl[0], wdp_general_buffer.pl_length);     
    *length = wdp_general_buffer.pl_length;
    return true;
  }
  else
  {
    return false;       
  }
} 

bool wdp_device_request_pairing()
{
  fap_goto_idle_mode();

  fap_set_channels(wdp_pairing_ch_tab);
  fap_set_output_power(WDP_PAIRING_OUTPUT_POWER);

  wdp_general_buffer.pl[WDP_TYPE_ID] = PAIRING_REQ;
  wdp_general_buffer.pl[DEV_TYPE] = wdp_dev_dynamic_setup.dev_type;
  wdp_general_buffer.pipe = WDP_PAIRING_PIPE;
  wdp_general_buffer.pl_length = WDP_PAIRING_PL_LENGTH;
  
  wdp_transmit_tx_buffer_and_wait(&wdp_general_buffer, WDP_TX_PAIRING_TIMEOUT);
  fap_flush_rx_fifo();

  if(wdp_transmit_tx_buffer_and_wait(&wdp_general_buffer, WDP_TX_PAIRING_TIMEOUT))
  { 
    if(fap_read_rx_fifo(&wdp_general_buffer))
    {        
      if(wdp_general_buffer.pl[WDP_TYPE_ID] == PAIRING_RESP && 
      wdp_general_buffer.pl[DEV_TYPE] == wdp_dev_dynamic_setup.dev_type)
      {
        fap_set_address(FAP_DEVICE1, &wdp_general_buffer.pl[ADDRESS_B0]);
      
        wdp_general_buffer.pl[WDP_TYPE_ID] = PAIRING_COMPL;         // Pairing complete
        wdp_general_buffer.pipe = WDP_PAIRING_PIPE;
        wdp_general_buffer.pl_length = WDP_PAIRING_PL_LENGTH;
 
        if(wdp_transmit_tx_buffer_and_wait(&wdp_general_buffer, WDP_TX_PAIRING_TIMEOUT))
        {
          fap_flush_rx_fifo();

          fap_get_address(FAP_DEVICE1, &wdp_general_buffer.pl[0]);  
          wdp_extract_channels(wdp_general_buffer.pl[1], &wdp_general_buffer.pl[0]);
          fap_set_channels(&wdp_general_buffer.pl[0]);
         
          fap_set_output_power(FAP_OUTPUT_POWER);
          
          return true;
        }
      }
    }
  }
  
  fap_flush_rx_fifo();

  fap_get_address(FAP_DEVICE1, &wdp_general_buffer.pl[0]);  
  wdp_extract_channels(wdp_general_buffer.pl[1], &wdp_general_buffer.pl[0]);
  fap_set_channels(&wdp_general_buffer.pl[0]);
  
  fap_set_output_power(FAP_OUTPUT_POWER);
 
  return false;
}

void wdp_device_set_address(uint8_t *adr)
{
  fap_set_address(FAP_DEVICE1, adr);
  wdp_extract_channels(adr[1], &wdp_general_buffer.pl[0]);
  fap_set_channels(&wdp_general_buffer.pl[0]);
}
  
bool wdp_device_connected(void)
{
  return WDP_GET_BIT(wdp_status, WDP_CONNECTED);
} 
 
// Internal

bool wdp_transmit_tx_buffer_and_wait(fap_tx_rx_struct_t *datainput, uint16_t tx_timeout)
{
  wdp_keep_alive_counter=0;             // Reset keep-alive counter whenever data transmitted
  fap_tx_data(datainput, tx_timeout);   // Send packet      
  
  while(fap_get_mode()!= FAP_IDLE);     // Wait until finished
  
  if(fap_tx_success())
  {
    return true;
  }
  else
  {    
    return false;
  }
}

void wdp_send_keep_alive()
{
  wdp_general_buffer.pl[WDP_TYPE_ID] = KEEP_ALIVE;
  wdp_general_buffer.pipe = FAP_DEVICE1;
  wdp_general_buffer.pl_length = WDP_TYPE_ID_ONLY_PL;

  if(fap_get_mode() == FAP_IDLE)
  {
    fap_tx_data(&wdp_general_buffer, WDP_TX_DATA_TIMEOUT);   // Send packet      
  }
}
